package p.ithorns.sample.influxdb.core;

import com.influxdb.client.BucketsApi;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.InfluxQLQueryApi;
import com.influxdb.client.domain.*;
import com.influxdb.query.InfluxQLQueryResult;
import lombok.extern.slf4j.Slf4j;
import p.ithorns.framework.common.utils.TypeConverter;
import p.ithorns.sample.influxdb.Home;
import p.ithorns.sample.influxdb.mapper.QLResultBeanMapper;

import java.lang.reflect.Field;
import java.time.DateTimeException;
import java.time.Instant;
import java.util.*;
import java.util.function.Function;

/**
 * InfluxDbHelper
 * TODO
 *
 * @author HtL
 * @date 2025/1/16 17:05
 * @since 1.0.0
 */
@Slf4j
public class InfluxDbHelper {

    private final InfluxDBClient influxClient;

    public InfluxDbHelper(InfluxDBClient influxClient) {
        this.influxClient = influxClient;
        registerInstantConverter();
    }

    public String createBucketAndGetToken(String orgId, String bucketName) {
        // Create bucket "iot_bucket" with data retention set to 3,600 seconds
        BucketRetentionRules retention = new BucketRetentionRules();
        retention.setEverySeconds(3600 * 12);
        BucketsApi bucketsApi = influxClient.getBucketsApi();

        Bucket bucket = bucketsApi.findBucketByName(bucketName);
        if (null == bucket) {
            bucket = bucketsApi.createBucket(bucketName, orgId);
        }

        // Create access token to "iot_bucket"
        PermissionResource resource = new PermissionResource();
        resource.setId(bucket.getId());
        resource.setOrgID(orgId);
        resource.setType(PermissionResource.TYPE_BUCKETS);

        // Read permission
        Permission read = new Permission();
        read.setResource(resource);
        read.setAction(Permission.ActionEnum.READ);

        // Write permission
        Permission write = new Permission();
        write.setResource(resource);
        write.setAction(Permission.ActionEnum.WRITE);

        Authorization authorization = influxClient.getAuthorizationsApi()
                .createAuthorization(orgId, Arrays.asList(read, write));

        // Created token that can be use for writes to "iot_bucket"
        String token = authorization.getToken();
        System.out.println("Token: " + token);
        return token;
    }

    public void readByQL(Class<?> clazz, String bucket, String query) {
        InfluxQLQuery qlQuery = new InfluxQLQuery(query, bucket);
        InfluxQLQueryApi qlApi = influxClient.getInfluxQLQueryApi();
        InfluxQLQueryResult iqr = qlApi.query(qlQuery);
        List<InfluxQLQueryResult.Result> queryResults = iqr.getResults();

        QLResultBeanMapper converter = new QLResultBeanMapper();

        List<Object> results = new ArrayList<>();
        for (InfluxQLQueryResult.Result result : queryResults) {
            List<InfluxQLQueryResult.Series> series = result.getSeries();
            for (InfluxQLQueryResult.Series qls : series) {
                // String measurement = qls.getName();

                // 合并所有字段
                final Set<String> fields = qls.getColumns().keySet();
                fields.addAll(qls.getTags().keySet());

                List<InfluxQLQueryResult.Series.Record> seriesValues = qls.getValues();
                for (InfluxQLQueryResult.Series.Record record : seriesValues) {
                    Object obj =  toBean(Home.class, qls.getColumns(),
                            (index) -> record.getValues()[index]);
                    results.add(obj);
                }
            }
        }
        System.out.println(results);
    }

    public <T> T toBean(Class<T> clazz, Map<String, Integer> fields,
                        Function<Integer, Object> valueGetter) {
        Map<String, java.lang.reflect.Field> classFields = BeanUtil.getClassFields(clazz);
        final T obj = BeanUtil.instance(clazz);
        Set<Map.Entry<String, Integer>> entries = fields.entrySet();
        for (Map.Entry<String, Integer> entry : entries) {
            Field field = classFields.get(entry.getKey());
            if (null != field) {
                Integer index = entry.getValue();
                Object value = valueGetter.apply(index);
                setValue(obj, field, value);
            }
        }
        return   obj;
    }

    private <T> void setValue(T obj, Field field, Object value) {
        try {
            Class<?> valueType = field.getType();
            Object castValue = TypeConverter.convert(value, valueType);
            field.set(obj, castValue);
        } catch (IllegalArgumentException e) {
            log.error("参数类型不匹配:{}", value, e);
        } catch (IllegalAccessException e) {
            log.error("参数异常:{}", value, e);
        }
    }

    private void registerInstantConverter() {
        TypeConverter.registerConverter(Instant.class, value -> {
            if (value instanceof Number) {
                return Instant.ofEpochMilli(((Number) value).longValue());
            } else if (value instanceof String str) {
                if (TypeConverter.isNullStr(str)) {
                    return null;
                }
                try {
                    long l = Long.parseLong(str);
                    return Instant.ofEpochMilli(l / 1000000);
                } catch (DateTimeException e) {
                    throw new IllegalArgumentException("Failed to parse " + value + " to Instant", e);
                }
            } else {
                throw new IllegalArgumentException("Unsupported conversion from " + value.getClass() + " to Integer");
            }
        });
    }

}